package repository

import (
	"context"
	"fmt"
	"github.com/google/wire"
	"github.com/pkg/errors"
	"go.etcd.io/etcd/clientv3"
	"time"
)

type EtcdRepository interface {
	Get(client *clientv3.Client, key string) (string, error)
	List(client *clientv3.Client, key string) ([]string, error)
	Create(client *clientv3.Client, key, val string) error
	Update(client *clientv3.Client, key, val string) error
	ListResp(client *clientv3.Client, key string) (*clientv3.GetResponse, error)
	GetResp(client *clientv3.Client, key string) (*clientv3.GetResponse, error)
	Delete(client *clientv3.Client, key string) error
}

type etcdRepository struct {
}

func NewEtcdRepository() EtcdRepository {
	return &etcdRepository{}
}

func (r etcdRepository) Get(client *clientv3.Client, key string) (string, error) {
	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
	resp, err := client.Get(ctx, key)
	if err != nil {
		return "", fmt.Errorf("etcd get failed: %w", err)
	}
	if resp.Count == 0 {
		return "", fmt.Errorf("key: %s is not found", key)
	}

	return string(resp.Kvs[0].Value), nil
}

func (r etcdRepository) GetResp(client *clientv3.Client, key string) (*clientv3.GetResponse, error) {
	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
	resp, err := client.Get(ctx, key)
	if err != nil {
		return nil, fmt.Errorf("etcd get failed: %w", err)
	}
	if resp.Count == 0 {
		return nil, fmt.Errorf("key: %s is not found", key)
	}

	return resp, nil
}

func (r *etcdRepository) List(client *clientv3.Client, key string) ([]string, error) {
	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
	resp, err := client.Get(ctx, key, clientv3.WithPrefix())
	if err != nil {
		return nil, fmt.Errorf("etcd get failed: %w", err)
	}
	var ret []string
	for i := range resp.Kvs {
		ret = append(ret, string(resp.Kvs[i].Value))
	}

	return ret, nil
}

func (r *etcdRepository) ListResp(client *clientv3.Client, key string) (*clientv3.GetResponse, error) {
	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
	resp, err := client.Get(ctx, key, clientv3.WithPrefix())
	if err != nil {
		return nil, fmt.Errorf("etcd get failed: %w", err)
	}
	return resp, nil
}

func (r *etcdRepository) Create(client *clientv3.Client, key, val string) error {
	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
	_, err := client.Put(ctx, key, val)
	if err != nil {
		return errors.Wrap(err, "call etcd Create failed")
	}
	return nil
}

func (r *etcdRepository) Update(client *clientv3.Client, key, val string) error {
	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
	_, err := client.Put(ctx, key, val, clientv3.WithPrevKV())
	if err != nil {
		return errors.Wrap(err, "call etcd Update failed")
	}
	return nil
}

func (r *etcdRepository) Delete(client *clientv3.Client, key string) error {
	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
	resp, err := client.Delete(ctx, key)
	if err != nil {
		return errors.Wrap(err, "call etcd Delete failed")
	}
	if resp.Deleted == 0 {
		return errors.Wrap(err, "key don't exist")
	}
	return nil
}

var ProviderSet = wire.NewSet(
	NewEtcdRepository,
)
